package com.peak.spring.boot.core.manager;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Lists;
import com.peak.spring.boot.api.constant.ErrorMessage;
import com.peak.spring.boot.api.entity.Identity;
import com.peak.spring.boot.api.entity.LogicalDeleteEntity;
import com.peak.spring.boot.api.manager.BaseManager;
import com.peak.spring.boot.api.repository.BaseRepository;
import com.peak.spring.boot.api.repository.FilterParam;
import com.peak.spring.boot.builder.repository.FilterParamBuilder;
import com.peak.spring.boot.builder.repository.SpecificationsBuilder;
import com.peak.spring.boot.core.exception.Exceptions;
import com.peak.spring.boot.tools.Converts;
import com.peak.spring.boot.tools.Dates;
import com.peak.spring.boot.tools.Emptys;
import com.peak.spring.boot.tools.Reflections;

@Component
@Transactional(readOnly = true)
public abstract class BaseManagerImpl<T extends Identity> implements BaseManager<T> {

	@Autowired
	protected BaseRepository<T> repository;

	protected Class<T> entityClass;

	public Class<T> getEntityClass() {
		if (this.entityClass == null) {
			this.entityClass = Reflections.getClassGenricType(this.getClass());
		}
		return this.entityClass;
	}

	@Override
	public T getOne(Number id) {
		try {
			return repository.getOne((Number) Converts.toByClass(id, Reflections.getFieldClass(getEntityClass(), "id")));
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public T findOne(Number id) {
		try {
			return repository.findOne((Number) Converts.toByClass(id, Reflections.getFieldClass(getEntityClass(), "id")));
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public List<T> findAll() {
		try {
			return findList(Lists.newArrayList());
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public List<T> findAll(Iterable<Number> ids) {
		try {
			return repository.findAll(ids);
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public List<T> findAll(Sort sort) {
		try {
			return findList(Lists.newArrayList(), sort);
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public Page<T> findPage(Pageable pageable) {
		try {
			return findPage(Lists.newArrayList(), pageable);
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public T findOne(String fieldName, Object value) {
		try {
			List<FilterParam> filterParams = FilterParamBuilder.create().eq(fieldName, value).build();
			if (isLogicDelete()) {
				filterParams.add(LogicalDeleteEntity.addFilterDeleted());
			}
			return repository.findOne(SpecificationsBuilder.build(filterParams));
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public T findOne(List<FilterParam> filterParams) {
		try {
			if (isLogicDelete()) {
				filterParams.add(LogicalDeleteEntity.addFilterDeleted());
			}
			return repository.findOne(SpecificationsBuilder.build(filterParams));
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public T findFirstOne(List<FilterParam> filterParams) {
		Page<T> page = findPage(filterParams, new PageRequest(0, 1));
		if (page.getTotalElements() > 0) {
			return page.getContent().get(0);
		} else {
			return null;
		}
	}

	@Override
	public T findFirstOne(List<FilterParam> filterParams, Sort sort) {
		Page<T> page = findPage(filterParams, new PageRequest(0, 1, sort));
		if (page.getTotalElements() > 0) {
			return page.getContent().get(0);
		} else {
			return null;
		}
	}

	@Override
	public List<T> findList(List<FilterParam> filterParams) {
		try {
			if (isLogicDelete()) {
				filterParams.add(LogicalDeleteEntity.addFilterDeleted());
			}
			return repository.findAll(SpecificationsBuilder.build(filterParams));
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public List<T> findList(List<FilterParam> filterParams, Sort sort) {
		try {
			if (isLogicDelete()) {
				filterParams.add(LogicalDeleteEntity.addFilterDeleted());
			}
			return repository.findAll(SpecificationsBuilder.build(filterParams), sort);
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public Page<T> findPage(List<FilterParam> filterParams, Pageable pageable) {
		try {
			if (isLogicDelete()) {
				filterParams.add(LogicalDeleteEntity.addFilterDeleted());
			}
			return repository.findAll(SpecificationsBuilder.build(filterParams), pageable);
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public int count(List<FilterParam> filterParams) {
		try {
			if (isLogicDelete()) {
				filterParams.add(LogicalDeleteEntity.addFilterDeleted());
			}
			return (int) repository.count(SpecificationsBuilder.build(filterParams));
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	public boolean checkUnique(List<FilterParam> filterParams, T entity) {
		try {
			if (isLogicDelete()) {
				filterParams.add(LogicalDeleteEntity.addFilterDeleted());
			}
			List<T> dbEntity = repository.findAll(SpecificationsBuilder.build(filterParams));
			if (Emptys.isNotEmpty(dbEntity)) {
				if (dbEntity.size() > 1) {
					return false;
				} else {
					if (!dbEntity.get(0).getId().equals(entity.getId())) {
						return false;
					}
				}
			}
			return true;
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_FIND_FAIL);
		}
	}

	@Override
	@Transactional
	public <S extends T> S save(S entity) {
		try {
			return repository.save(entity);
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_OPERATE_FAIL);
		}
	}

	@Override
	@Transactional
	public <S extends T> List<S> save(Iterable<S> entities) {
		try {
			return repository.save(entities);
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_OPERATE_FAIL);
		}
	}

	@Override
	@Transactional
	public void delete(Number id) {
		try {
			delete(getOne(id));
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_OPERATE_FAIL);
		}
	}

	@Override
	@Transactional
	public void delete(T entity) {
		try {
			if (entity instanceof LogicalDeleteEntity) {
				LogicalDeleteEntity dataEntity = (LogicalDeleteEntity) entity;
				dataEntity.setDelFlag(true);
				dataEntity.setDelDateTime(Dates.now());
				save(entity);
			} else {
				repository.delete(entity);
			}
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_OPERATE_FAIL);
		}
	}

	@Override
	@Transactional
	public void delete(Iterable<T> entities) {
		try {
			if (Emptys.isNotEmpty(entities) && entities.iterator().hasNext()) {// 等于null的和空列表不抛异常，直接过。
				boolean delFlag = false;
				for (T entity : entities) {
					if (entity instanceof LogicalDeleteEntity) {
						LogicalDeleteEntity dataEntity = (LogicalDeleteEntity) entity;
						dataEntity.setDelFlag(true);
						dataEntity.setDelDateTime(Dates.now());
						delFlag = true;
					}
				}
				if (delFlag) {
					save(entities);
				} else {
					repository.deleteInBatch(entities);
				}
			}
		} catch (Exception e) {
			throw Exceptions.unchecked(e, ErrorMessage.DATA_OPERATE_FAIL);
		}
	}

	protected boolean isLogicDelete() {
		return LogicalDeleteEntity.class.isAssignableFrom(getEntityClass());
	}

}
